﻿using IOP.OpenType.Tables;
using IOP.OpenType;
using IOP.OpenType.Abstractions;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace IOP.Codec.OpenType
{
    /// <summary>
    /// 字形表加载器
    /// </summary>
    public class GlyfTableLoader
    {
        /// <summary>
        /// 加载
        /// </summary>
        /// <param name="stream">字节流</param>
        /// <param name="entry">表入口</param>
        /// <param name="offset">表偏移</param>
        /// <param name="length">表长度</param>
        /// <returns></returns>
        public async Task<GlyphDescription> LoadGlyfData(FileStream stream, TableEntry entry, uint offset, uint length)
        {
            uint finalOffset = entry.Offset + offset;
            stream.Seek(finalOffset, SeekOrigin.Begin);
            var pipe = new Pipe();
            var w = WriteToPipe(stream, pipe.Writer, entry, length);
            var r = ReadFromPipe(pipe.Reader);
            await Task.WhenAll(w, r);
            GlyphDescription description = r.Result;
            return description;
        }
        /// <summary>
        /// 读取字形
        /// </summary>
        /// <param name="stream">字节流</param>
        /// <param name="entry">表入口</param>
        /// <param name="offset">表偏移</param>
        /// <param name="length">表长度</param>
        /// <returns></returns>
        public async Task<Glyph> LoadGlyph(FileStream stream, TableEntry entry, uint offset, uint length)
        {
            uint finalOffset = entry.Offset + offset;
            stream.Seek(finalOffset, SeekOrigin.Begin);
            var pipe = new Pipe();
            var w = WriteToPipe(stream, pipe.Writer, entry, length);
            var r = ReadFromPipe(pipe.Reader);
            await Task.WhenAll(w, r);
            var description = r.Result;
            Glyph glyph = CreateGlyph(description);
            return glyph;
        }
        /// <summary>
        /// 读取字形
        /// </summary>
        /// <param name="description"></param>
        /// <returns></returns>
        public Glyph LoadGlyph(GlyphDescription description)
        {
            Glyph glyph = CreateGlyph(description);
            return glyph;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private async Task WriteToPipe(Stream stream, PipeWriter writer, TableEntry entry, uint length)
        {
            try
            {
                long already = 0;
                while (true)
                {
                    if (length <= 0) break;
                    Memory<byte> memory = writer.GetMemory(2048);
                    int bytesRead = await stream.ReadAsync(memory);
                    writer.Advance(bytesRead);
                    already += bytesRead;
                    if (bytesRead == 0 || already >= length) break;
                    FlushResult result = await writer.FlushAsync();
                    if (result.IsCompleted) break;
                }
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                writer.Complete();
            }
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private async Task<GlyphDescription> ReadFromPipe(PipeReader reader)
        {
            try
            {
                GlyphDescription description = null;
                long byteIndex = 0;
                long consumed = 0;
                ReadResult result = await reader.ReadAsync();
                ReadOnlySequence<byte> buffer = result.Buffer;
                if (buffer.IsEmpty) return description;
                var header = ReadHeader(ref buffer, ref byteIndex);
                if (header.NumberOfContours > 0)
                {
                    var simple = new SimpleGlyphDescription();
                    simple.Header = header;
                    ushort[] endPtsOfContours = new ushort[header.NumberOfContours];
                    long local = (header.NumberOfContours + 1) * sizeof(ushort);
                    while (buffer.Length < local)
                    {
                        reader.AdvanceTo(buffer.GetPosition(byteIndex));
                        consumed += byteIndex;
                        byteIndex = 0;
                        result = await reader.ReadAsync();
                        buffer = result.Buffer;
                    }
                    ReadContours(ref buffer, ref byteIndex, endPtsOfContours);
                    simple.EndPtsOfContours = endPtsOfContours;

                    ushort iLength = ReadInstructionLength(ref buffer, ref byteIndex);
                    byte[] instructions = new byte[iLength];
                    while(buffer.Length - byteIndex < iLength)
                    {
                        reader.AdvanceTo(buffer.GetPosition(byteIndex));
                        consumed += byteIndex;
                        byteIndex = 0;
                        result = await reader.ReadAsync();
                        buffer = result.Buffer;
                    }
                    ReadInstructions(ref buffer, ref byteIndex, instructions);
                    simple.Instructions = instructions;

                    int pointNum = endPtsOfContours[header.NumberOfContours - 1] + 1;
                    byte[] flags = new byte[pointNum];
                    while (buffer.Length - byteIndex < pointNum)
                    {
                        reader.AdvanceTo(buffer.GetPosition(byteIndex));
                        consumed += byteIndex;
                        byteIndex = 0;
                        result = await reader.ReadAsync();
                        buffer = result.Buffer;
                    }
                    ReadFlags(ref buffer, ref byteIndex, flags);
                    simple.Flags = flags;

                    short[] xCoordinates = new short[pointNum];
                    while (buffer.Length - byteIndex < pointNum * sizeof(ushort))
                    {
                        reader.AdvanceTo(buffer.GetPosition(byteIndex));
                        consumed += byteIndex;
                        byteIndex = 0;
                        result = await reader.ReadAsync();
                        buffer = result.Buffer;
                    }
                    ReadXCoordinates(ref buffer, ref byteIndex, xCoordinates, flags);
                    simple.XCoordinates = xCoordinates;

                    short[] yCoordinates = new short[pointNum];
                    while(buffer.Length - byteIndex < pointNum * sizeof(ushort))
                    {
                        reader.AdvanceTo(buffer.GetPosition(byteIndex));
                        consumed += byteIndex;
                        byteIndex = 0;
                        result = await reader.ReadAsync();
                        buffer = result.Buffer;
                    }
                    ReadYCoordinates(ref buffer, ref byteIndex, yCoordinates, flags);
                    simple.YCoordinates = yCoordinates;
                    consumed += byteIndex;
                    description = simple;
                }
                else throw new NotSupportedException("composite glyph is not supported");
                return description;
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                reader.Complete();
            }
        }
        /// <summary>
        /// 读取描述头
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="byteIndex"></param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private GlyphHeader ReadHeader(ref ReadOnlySequence<byte> buffer, ref long byteIndex)
        {
            SequenceReader<byte> reader = new SequenceReader<byte>(buffer);
            reader.Advance(byteIndex);
            reader.TryReadBigEndian(out short numberOfContours);
            reader.TryReadBigEndian(out short xMin);
            reader.TryReadBigEndian(out short yMin);
            reader.TryReadBigEndian(out short xMax);
            reader.TryReadBigEndian(out short yMax);
            byteIndex += 10;
            GlyphHeader header = new GlyphHeader
            {
                NumberOfContours = numberOfContours,
                XMax = xMax,
                XMin = xMin,
                YMax = yMax,
                YMin = yMin
            };
            return header;
        }
        /// <summary>
        /// 读取控制点数据
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="byteIndex"></param>
        /// <param name="contours"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void ReadContours(ref ReadOnlySequence<byte> buffer, ref long byteIndex, ushort[] contours)
        {
            SequenceReader<byte> reader = new SequenceReader<byte>(buffer);
            reader.Advance(byteIndex);
            for(int i = 0; i < contours.Length; i++)
            {
                reader.TryReadBigEndian(out short c);
                contours[i] = (ushort)c;
            }
            byteIndex += contours.Length * sizeof(ushort);
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private ushort ReadInstructionLength(ref ReadOnlySequence<byte> buffer, ref long byteIndex)
        {
            SequenceReader<byte> reader = new SequenceReader<byte>(buffer);
            reader.Advance(byteIndex);
            reader.TryReadBigEndian(out short i);
            byteIndex += sizeof(short);
            return (ushort)i;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void ReadInstructions(ref ReadOnlySequence<byte> buffer, ref long byteIndex, byte[] instructions)
        {
            SequenceReader<byte> reader = new SequenceReader<byte>(buffer);
            reader.Advance(byteIndex);
            for(int i = 0; i < instructions.Length; i++)
            {
                reader.TryRead(out byte v);
                instructions[i] = v;
            }
            byteIndex += instructions.Length;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void ReadFlags(ref ReadOnlySequence<byte> buffer, ref long byteIndex, byte[] flags)
        {
            SequenceReader<byte> reader = new SequenceReader<byte>(buffer);
            reader.Advance(byteIndex);
            byte repeat = 0;
            byte local = 0;
            for (int i = 0; i < flags.Length; i++)
            {
                if(repeat == 0)
                {
                    reader.TryRead(out byte l);
                    byteIndex++;
                    flags[i] = l;
                    if((l & 0x8) > 0)
                    {
                        reader.TryRead(out repeat);
                        byteIndex++;
                        local = l;
                    }
                }
                else
                {
                    flags[i] = local;
                    repeat--;
                }
            }
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void ReadXCoordinates(ref ReadOnlySequence<byte> buffer, ref long byteIndex, short[] xCoordinates, byte[] flags)
        {
            SequenceReader<byte> reader = new SequenceReader<byte>(buffer);
            reader.Advance(byteIndex);
            for(int i = 0; i < xCoordinates.Length; i++)
            {
                SimpleGlyphFlags f = (SimpleGlyphFlags)flags[i];
                if(!f.HasFlag(SimpleGlyphFlags.X_SHORT_VECTOR) && f.HasFlag(SimpleGlyphFlags.X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR))
                {
                    xCoordinates[i] = i > 0 ? xCoordinates[i - 1] : (short)0;
                }
                else
                {
                    if (f.HasFlag(SimpleGlyphFlags.X_SHORT_VECTOR))
                    {
                        reader.TryRead(out byte v);
                        byteIndex++;
                        xCoordinates[i] = v;
                        if (!f.HasFlag(SimpleGlyphFlags.X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR))
                            xCoordinates[i] *= -1;
                    }
                    else
                    {
                        reader.TryReadBigEndian(out short v);
                        byteIndex += sizeof(short);
                        xCoordinates[i] = v;
                    }
                    if (i > 0) xCoordinates[i] += xCoordinates[i - 1];
                }
            }
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void ReadYCoordinates(ref ReadOnlySequence<byte> buffer, ref long byteIndex, short[] yCoordinates, byte[] flags)
        {
            SequenceReader<byte> reader = new SequenceReader<byte>(buffer);
            reader.Advance(byteIndex);
            for (int i = 0; i < yCoordinates.Length; i++)
            {
                SimpleGlyphFlags f = (SimpleGlyphFlags)flags[i];
                if (!f.HasFlag(SimpleGlyphFlags.Y_SHORT_VECTOR) && f.HasFlag(SimpleGlyphFlags.Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR))
                {
                    yCoordinates[i] = i > 0 ? yCoordinates[i - 1] : (short)0;
                }
                else
                {
                    if (f.HasFlag(SimpleGlyphFlags.Y_SHORT_VECTOR))
                    {
                        reader.TryRead(out byte v);
                        byteIndex++;
                        yCoordinates[i] = v;
                        if (!f.HasFlag(SimpleGlyphFlags.Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR))
                            yCoordinates[i] *= -1;
                    }
                    else
                    {
                        reader.TryReadBigEndian(out short v);
                        byteIndex += sizeof(short);
                        yCoordinates[i] = v;
                    }
                    if (i > 0) yCoordinates[i] += yCoordinates[i - 1];
                }
            }
        }
        /// <summary>
        /// 创建字形
        /// </summary>
        /// <param name="description"></param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private Glyph CreateGlyph(GlyphDescription description)
        {
            Glyph glyph = new Glyph();
            if(description == null)
            {
                glyph.Contours = new Contour[0];
                glyph.Curves = new Curve[0];
                return glyph;
            }
            var header = description.Header;
            glyph.Header = header;
            if (header.NumberOfContours > 0)
            {
                SimpleGlyphDescription simpleGlyph = description as SimpleGlyphDescription;
                var contours = new Contour[header.NumberOfContours];
                var flags = simpleGlyph.Flags;
                var endContours = simpleGlyph.EndPtsOfContours;
                var xCoord = simpleGlyph.XCoordinates;
                var yCoord = simpleGlyph.YCoordinates;
                short x = xCoord[0];
                short y = yCoord[0];
                ushort start;
                ushort end;
                ContourPoint[] points;
                int index;
                SimpleGlyphFlags flag;
                for (int i = 0; i < header.NumberOfContours; i++)
                {
                    if (i == 0)
                    {
                        start = 1;
                        end = endContours[i];
                        points = new ContourPoint[end + 1];
                        flag = (SimpleGlyphFlags)flags[0];
                        points[0] = new ContourPoint { Flag = flag, Point = new Vector2(x, y) };
                        index = 1;
                    }
                    else
                    {
                        start = (ushort)(endContours[i - 1] + 1);
                        end = endContours[i];
                        points = new ContourPoint[(end + 1) - start];
                        index = 0;
                    }
                    for (int m = start; m < end + 1; m++)
                    {
                        x = xCoord[m];
                        y = yCoord[m];
                        flag = (SimpleGlyphFlags)flags[m];
                        points[index++] = new ContourPoint { Flag = flag, Point = new Vector2(x, y) };
                    }
                    contours[i] = new Contour { Points = points };
                }
                glyph.Contours = contours;
                Curve[] curves = CreateCurves(contours);
                glyph.Curves = curves;
                return glyph;
            }
            else throw new NotSupportedException("composite glyph is not supported");
        }
        /// <summary>
        /// 创建曲线集合
        /// </summary>
        /// <param name="contours"></param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private Curve[] CreateCurves(Contour[] contours)
        {
            if (contours == null) throw new ArgumentNullException(nameof(contours));
            Curve[] r = new Curve[contours.Length];
            for(int i = 0; i < r.Length; i++)
            {
                Curve c = new Curve();
                List<Vector2[]> curveSegment = new List<Vector2[]>();
                ContourPoint[] points = contours[i].Points;
                ContourPoint first = points[0];
                Vector2 prePoint = default;
                if (!first.Flag.HasFlag(SimpleGlyphFlags.ON_CURVE_POINT))
                {
                    ContourPoint last = points[^1];
                    if (last.Flag.HasFlag(SimpleGlyphFlags.ON_CURVE_POINT))
                        prePoint = last.Point;
                    else
                        prePoint = new Vector2((first.Point.X + last.Point.X) / 2, (first.Point.Y + last.Point.Y) / 2);
                }

                int pointNum = points.Length;
                for (int j = 0; j < points.Length; j++)
                {
                    ContourPoint p0 = points[j % pointNum];
                    ContourPoint p1 = points[(j + 1) % pointNum];
                    if (p0.Flag.HasFlag(SimpleGlyphFlags.ON_CURVE_POINT))
                    {
                        if (p1.Flag.HasFlag(SimpleGlyphFlags.ON_CURVE_POINT))
                        {
                            Vector2[] vectors = new Vector2[] { p0.Point, p1.Point };
                            curveSegment.Add(vectors);
                            prePoint = p0.Point;
                            continue;
                        }
                        else
                        {
                            j++;
                            ContourPoint p2 = points[(j + 1) % pointNum];
                            if (p2.Flag.HasFlag(SimpleGlyphFlags.ON_CURVE_POINT))
                            {
                                Vector2[] vectors = new Vector2[] { p0.Point, p1.Point, p2.Point };
                                curveSegment.Add(vectors);
                                prePoint = p1.Point;
                                continue;
                            }
                            else
                            {
                                Vector2 diff = new Vector2((p1.Point.X + p2.Point.X) / 2, (p1.Point.Y + p2.Point.Y) / 2);
                                Vector2[] vectors = new Vector2[] { p0.Point, p1.Point, diff };
                                curveSegment.Add(vectors);
                                prePoint = diff;
                                continue;
                            }
                        }
                    }
                    else
                    {
                        if (p1.Flag.HasFlag(SimpleGlyphFlags.ON_CURVE_POINT))
                        {
                            Vector2[] vectors = new Vector2[] { prePoint, p0.Point, p1.Point };
                            curveSegment.Add(vectors);
                            prePoint = p0.Point;
                            continue;
                        }
                        else
                        {
                            Vector2 diff = new Vector2((p0.Point.X + p1.Point.X) / 2, (p0.Point.Y + p1.Point.Y) / 2);
                            Vector2[] vectors = new Vector2[] { prePoint, p0.Point, diff };
                            curveSegment.Add(vectors);
                            prePoint = diff;
                            continue;
                        }
                    }
                }
                c.CurveSegment = curveSegment.ToArray();
                r[i] = c;
            }
            return r;
        }
    }
}
